use axum::{
  extract::rejection::{JsonRejection, PathRejection, QueryRejection},
  http::StatusCode,
  response::{IntoResponse, Response},
};

use crate::response::ApiResponse;

pub type ApiResult<T> = Result<T, ApiError>;

#[derive(Debug, thiserror::Error)]
pub enum ApiError {
  #[error("Not Found")]
  NotFound,
  #[error("Method not allowed")]
  MethodNotAllowed,
  #[error("Database Error: {0}")]
  Database(#[from] sea_orm::DbErr),
  #[error("query params error: {0}")]
  Query(#[from] QueryRejection),
  #[error("{0}")]
  Biz(String),
  #[error("Error: {0}")]
  Internal(#[from] anyhow::Error),
  #[error("Path params error: {0}")]
  Path(#[from] PathRejection),
  #[error("Body params json paser error: {0}")]
  Json(#[from] JsonRejection),
}

impl ApiError {
  pub fn status_code(&self) -> StatusCode {
    match self {
      Self::NotFound => StatusCode::NOT_FOUND,
      Self::MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED,
      Self::Internal(_) | Self::Database(_) => {
        StatusCode::INTERNAL_SERVER_ERROR
      }
      Self::Query(_) | Self::Path(_) | Self::Json(_) => StatusCode::BAD_REQUEST,
      Self::Biz(_) => StatusCode::OK,
    }
  }
}

impl IntoResponse for ApiError {
  fn into_response(self) -> Response {
    let status_code = self.status_code();
    let body = axum::Json(ApiResponse::<()>::err(self.to_string()));

    (status_code, body).into_response()
  }
}
